home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / tcl / tcl67.lha / tcl6.7 / tclHistory.c < prev    next >
C/C++ Source or Header  |  1992-12-09  |  31KB  |  1,097 lines

  1. /* 
  2.  * tclHistory.c --
  3.  *
  4.  *    This module implements history as an optional addition to Tcl.
  5.  *    It can be called to record commands ("events") before they are
  6.  *    executed, and it provides a command that may be used to perform
  7.  *    history substitutions.
  8.  *
  9.  * Copyright 1990-1991 Regents of the University of California
  10.  * Permission to use, copy, modify, and distribute this
  11.  * software and its documentation for any purpose and without
  12.  * fee is hereby granted, provided that the above copyright
  13.  * notice appear in all copies.  The University of California
  14.  * makes no representations about the suitability of this
  15.  * software for any purpose.  It is provided "as is" without
  16.  * express or implied warranty.
  17.  */
  18.  
  19. #ifndef lint
  20. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclHistory.c,v 1.24 92/12/09 16:54:52 ouster Exp $ SPRITE (Berkeley)";
  21. #endif /* not lint */
  22.  
  23. #include "tclInt.h"
  24.  
  25. /*
  26.  * This history stuff is mostly straightforward, except for one thing
  27.  * that makes everything very complicated.  Suppose that the following
  28.  * commands get executed:
  29.  *    echo foo
  30.  *    history redo
  31.  * It's important that the history event recorded for the second command
  32.  * be "echo foo", not "history redo".  Otherwise, if another "history redo"
  33.  * command is typed, it will result in infinite recursions on the
  34.  * "history redo" command.  Thus, the actual recorded history must be
  35.  *    echo foo
  36.  *    echo foo
  37.  * To do this, the history command revises recorded history as part of
  38.  * its execution.  In the example above, when "history redo" starts
  39.  * execution, the current event is "history redo", but the history
  40.  * command arranges for the current event to be changed to "echo foo".
  41.  *
  42.  * There are three additional complications.  The first is that history
  43.  * substitution may only be part of a command, as in the following
  44.  * command sequence:
  45.  *    echo foo bar
  46.  *    echo [history word 3]
  47.  * In this case, the second event should be recorded as "echo bar".  Only
  48.  * part of the recorded event is to be modified.  Fortunately, Tcl_Eval
  49.  * helps with this by recording (in the evalFirst and evalLast fields of
  50.  * the intepreter) the location of the command being executed, so the
  51.  * history module can replace exactly the range of bytes corresponding
  52.  * to the history substitution command.
  53.  *
  54.  * The second complication is that there are two ways to revise history:
  55.  * replace a command, and replace the result of a command.  Consider the
  56.  * two examples below:
  57.  *    format {result is %d} $num       |    format {result is %d} $num
  58.  *    print [history redo]           |    print [history word 3]
  59.  * Recorded history for these two cases should be as follows:
  60.  *    format {result is %d} $num       |    format {result is %d} $num
  61.  *    print [format {result is %d} $num] |    print $num
  62.  * In the left case, the history command was replaced with another command
  63.  * to be executed (the brackets were retained), but in the case on the
  64.  * right the result of executing the history command was replaced (i.e.
  65.  * brackets were replaced too).
  66.  *
  67.  * The third complication is that there could potentially be many
  68.  * history substitutions within a single command, as in:
  69.  *    echo [history word 3] [history word 2]
  70.  * There could even be nested history substitutions, as in:
  71.  *    history subs abc [history word 2]
  72.  * If history revisions were made immediately during each "history" command
  73.  * invocations, it would be very difficult to produce the correct cumulative
  74.  * effect from several substitutions in the same command.  To get around
  75.  * this problem, the actual history revision isn't made during the execution
  76.  * of the "history" command.  Information about the changes is just recorded,
  77.  * in xxx records, and the actual changes are made during the next call to
  78.  * Tcl_RecordHistory (when we know that execution of the previous command
  79.  * has finished).
  80.  */
  81.  
  82. /*
  83.  * Default space allocation for command strings:
  84.  */
  85.  
  86. #define INITIAL_CMD_SIZE 40
  87.  
  88. /*
  89.  * Forward declarations for procedures defined later in this file:
  90.  */
  91.  
  92. static void        DoRevs _ANSI_ARGS_((Interp *iPtr));
  93. static HistoryEvent *    GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));
  94. static char *        GetWords _ANSI_ARGS_((Interp *iPtr, char *command,
  95.                 char *words));
  96. static void        InsertRev _ANSI_ARGS_((Interp *iPtr,
  97.                 HistoryRev *revPtr));
  98. static void        MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));
  99. static void        RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));
  100. static void        RevResult _ANSI_ARGS_((Interp *iPtr, char *string));
  101. static int        SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,
  102.                 char *old, char *new));
  103.  
  104. /*
  105.  *----------------------------------------------------------------------
  106.  *
  107.  * Tcl_InitHistory --
  108.  *
  109.  *    Initialize history-related state in an interpreter.
  110.  *
  111.  * Results:
  112.  *    None.
  113.  *
  114.  * Side effects:
  115.  *    History info is initialized in iPtr.
  116.  *
  117.  *----------------------------------------------------------------------
  118.  */
  119.  
  120. void
  121. Tcl_InitHistory(interp)
  122.     Tcl_Interp *interp;        /* Interpreter to initialize. */
  123. {
  124.     register Interp *iPtr = (Interp *) interp;
  125.     int i;
  126.  
  127.     if (iPtr->numEvents != 0) {
  128.     return;
  129.     }
  130.     iPtr->numEvents = 20;
  131.     iPtr->events = (HistoryEvent *)
  132.         ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));
  133.     for (i = 0; i < iPtr->numEvents; i++) {
  134.     iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  135.     *iPtr->events[i].command = 0;
  136.     iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;
  137.     }
  138.     iPtr->curEvent = 0;
  139.     iPtr->curEventNum = 0;
  140.     Tcl_CreateCommand((Tcl_Interp *) iPtr, "history", Tcl_HistoryCmd,
  141.         (ClientData) NULL, (void (*)()) NULL);
  142. }
  143.  
  144. /*
  145.  *----------------------------------------------------------------------
  146.  *
  147.  * Tcl_RecordAndEval --
  148.  *
  149.  *    This procedure adds its command argument to the current list of
  150.  *    recorded events and then executes the command by calling Tcl_Eval.
  151.  *
  152.  * Results:
  153.  *    The return value is a standard Tcl return value, the result of
  154.  *    executing cmd.
  155.  *
  156.  * Side effects:
  157.  *    The command is recorded and executed.  In addition, pending history
  158.  *    revisions are carried out, and information is set up to enable
  159.  *    Tcl_Eval to identify history command ranges.  This procedure also
  160.  *    initializes history information for the interpreter, if it hasn't
  161.  *    already been initialized.
  162.  *
  163.  *----------------------------------------------------------------------
  164.  */
  165.  
  166. int
  167. Tcl_RecordAndEval(interp, cmd, flags)
  168.     Tcl_Interp *interp;        /* Token for interpreter in which command
  169.                  * will be executed. */
  170.     char *cmd;            /* Command to record. */
  171.     int flags;            /* Additional flags to pass to Tcl_Eval. 
  172.                  * TCL_NO_EVAL means only record: don't
  173.                  * execute command. */
  174. {
  175.     register Interp *iPtr = (Interp *) interp;
  176.     register HistoryEvent *eventPtr;
  177.     int length, result;
  178.  
  179.     if (iPtr->numEvents == 0) {
  180.     Tcl_InitHistory(interp);
  181.     }
  182.     DoRevs(iPtr);
  183.  
  184.     /*
  185.      * Don't record empty commands.
  186.      */
  187.  
  188.     while (isspace(*cmd)) {
  189.     cmd++;
  190.     }
  191.     if (*cmd == '\0') {
  192.     Tcl_ResetResult(interp);
  193.     return TCL_OK;
  194.     }
  195.  
  196.     iPtr->curEventNum++;
  197.     iPtr->curEvent++;
  198.     if (iPtr->curEvent >= iPtr->numEvents) {
  199.     iPtr->curEvent = 0;
  200.     }
  201.     eventPtr = &iPtr->events[iPtr->curEvent];
  202.  
  203.     /*
  204.      * Chop off trailing newlines before recording the command.
  205.      */
  206.  
  207.     length = strlen(cmd);
  208.     while (cmd[length-1] == '\n') {
  209.     length--;
  210.     }
  211.     MakeSpace(eventPtr, length + 1);
  212.     strncpy(eventPtr->command, cmd, length);
  213.     eventPtr->command[length] = 0;
  214.  
  215.     /*
  216.      * Execute the command.  Note: history revision isn't possible after
  217.      * a nested call to this procedure, because the event at the top of
  218.      * the history list no longer corresponds to what's going on when
  219.      * a nested call here returns.  Thus, must leave history revision
  220.      * disabled when we return.
  221.      */
  222.  
  223.     result = TCL_OK;
  224.     if (flags != TCL_NO_EVAL) {
  225.     iPtr->historyFirst = cmd;
  226.     iPtr->revDisables = 0;
  227.     result = Tcl_Eval(interp, cmd, flags | TCL_RECORD_BOUNDS,
  228.         (char **) NULL);
  229.     }
  230.     iPtr->revDisables = 1;
  231.     return result;
  232. }
  233.  
  234. /*
  235.  *----------------------------------------------------------------------
  236.  *
  237.  * Tcl_HistoryCmd --
  238.  *
  239.  *    This procedure is invoked to process the "history" Tcl command.
  240.  *    See the user documentation for details on what it does.
  241.  *
  242.  * Results:
  243.  *    A standard Tcl result.
  244.  *
  245.  * Side effects:
  246.  *    See the user documentation.
  247.  *
  248.  *----------------------------------------------------------------------
  249.  */
  250.  
  251.     /* ARGSUSED */
  252. int
  253. Tcl_HistoryCmd(dummy, interp, argc, argv)
  254.     ClientData dummy;            /* Not used. */
  255.     Tcl_Interp *interp;            /* Current interpreter. */
  256.     int argc;                /* Number of arguments. */
  257.     char **argv;            /* Argument strings. */
  258. {
  259.     register Interp *iPtr = (Interp *) interp;
  260.     register HistoryEvent *eventPtr;
  261.     int length;
  262.     char c;
  263.  
  264.     /*
  265.      * If no arguments, treat the same as "history info".
  266.      */
  267.  
  268.     if (argc == 1) {
  269.     goto infoCmd;
  270.     }
  271.  
  272.     c = argv[1][0];
  273.     length = strlen(argv[1]);
  274.  
  275.     if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {
  276.     if ((argc != 3) && (argc != 4)) {
  277.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  278.             " add event ?exec?\"", (char *) NULL);
  279.         return TCL_ERROR;
  280.     }
  281.     if (argc == 4) {
  282.         if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {
  283.         Tcl_AppendResult(interp, "bad argument \"", argv[3],
  284.             "\": should be \"exec\"", (char *) NULL);
  285.         return TCL_ERROR;
  286.         }
  287.         return Tcl_RecordAndEval(interp, argv[2], 0);
  288.     }
  289.     return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);
  290.     } else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {
  291.     if ((argc != 3) && (argc != 4)) {
  292.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  293.             " change newValue ?event?\"", (char *) NULL);
  294.         return TCL_ERROR;
  295.     }
  296.     if (argc == 3) {
  297.         eventPtr = &iPtr->events[iPtr->curEvent];
  298.         iPtr->revDisables += 1;
  299.         while (iPtr->revPtr != NULL) {
  300.         HistoryRev *nextPtr;
  301.  
  302.         ckfree(iPtr->revPtr->newBytes);
  303.         nextPtr = iPtr->revPtr->nextPtr;
  304.         ckfree((char *) iPtr->revPtr);
  305.         iPtr->revPtr = nextPtr;
  306.         }
  307.     } else {
  308.         eventPtr = GetEvent(iPtr, argv[3]);
  309.         if (eventPtr == NULL) {
  310.         return TCL_ERROR;
  311.         }
  312.     }
  313.     MakeSpace(eventPtr, strlen(argv[2]) + 1);
  314.     strcpy(eventPtr->command, argv[2]);
  315.     return TCL_OK;
  316.     } else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {
  317.     if (argc > 3) {
  318.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  319.             " event ?event?\"", (char *) NULL);
  320.         return TCL_ERROR;
  321.     }
  322.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  323.     if (eventPtr == NULL) {
  324.         return TCL_ERROR;
  325.     }
  326.     RevResult(iPtr, eventPtr->command);
  327.     Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);
  328.     return TCL_OK;
  329.     } else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {
  330.     int count, indx, i;
  331.     char *newline;
  332.  
  333.     if ((argc != 2) && (argc != 3)) {
  334.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  335.             " info ?count?\"", (char *) NULL);
  336.         return TCL_ERROR;
  337.     }
  338.     infoCmd:
  339.     if (argc == 3) {
  340.         if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  341.         return TCL_ERROR;
  342.         }
  343.         if (count > iPtr->numEvents) {
  344.         count = iPtr->numEvents;
  345.         }
  346.     } else {
  347.         count = iPtr->numEvents;
  348.     }
  349.     newline = "";
  350.     for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;
  351.         i < count; i++, indx++) {
  352.         char *cur, *next, savedChar;
  353.         char serial[20];
  354.  
  355.         if (indx >= iPtr->numEvents) {
  356.         indx -= iPtr->numEvents;
  357.         }
  358.         cur = iPtr->events[indx].command;
  359.         if (*cur == '\0') {
  360.         continue;        /* No command recorded here. */
  361.         }
  362.         sprintf(serial, "%6d  ", iPtr->curEventNum + 1 - (count - i));
  363.         Tcl_AppendResult(interp, newline, serial, (char *) NULL);
  364.         newline = "\n";
  365.  
  366.         /*
  367.          * Tricky formatting here:  for multi-line commands, indent
  368.          * the continuation lines.
  369.          */
  370.  
  371.         while (1) {
  372.         next = strchr(cur, '\n');
  373.         if (next == NULL) {
  374.             break;
  375.         }
  376.         next++;
  377.         savedChar = *next;
  378.         *next = 0;
  379.         Tcl_AppendResult(interp, cur, "\t", (char *) NULL);
  380.         *next = savedChar;
  381.         cur = next;
  382.         }
  383.         Tcl_AppendResult(interp, cur, (char *) NULL);
  384.     }
  385.     return TCL_OK;
  386.     } else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {
  387.     int count, i, src;
  388.     HistoryEvent *events;
  389.  
  390.     if (argc != 3) {
  391.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  392.             " keep number\"", (char *) NULL);
  393.         return TCL_ERROR;
  394.     }
  395.     if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
  396.         return TCL_ERROR;
  397.     }
  398.     if ((count <= 0) || (count > 1000)) {
  399.         Tcl_AppendResult(interp, "illegal keep count \"", argv[2],
  400.             "\"", (char *) NULL);
  401.         return TCL_ERROR;
  402.     }
  403.  
  404.     /*
  405.      * Create a new history array and copy as much existing history
  406.      * as possible from the old array.
  407.      */
  408.  
  409.     events = (HistoryEvent *)
  410.         ckalloc((unsigned) (count * sizeof(HistoryEvent)));
  411.     if (count < iPtr->numEvents) {
  412.         src = iPtr->curEvent + 1 - count;
  413.         if (src < 0) {
  414.         src += iPtr->numEvents;
  415.         }
  416.     } else {
  417.         src = iPtr->curEvent + 1;
  418.     }
  419.     for (i = 0; i < count; i++, src++) {
  420.         if (src >= iPtr->numEvents) {
  421.         src = 0;
  422.         }
  423.         if (i < iPtr->numEvents) {
  424.         events[i] = iPtr->events[src];
  425.         iPtr->events[src].command = NULL;
  426.         } else {
  427.         events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
  428.         events[i].command[0] = 0;
  429.         events[i].bytesAvl = INITIAL_CMD_SIZE;
  430.         }
  431.     }
  432.  
  433.     /*
  434.      * Throw away everything left in the old history array, and
  435.      * substitute the new one for the old one.
  436.      */
  437.  
  438.     for (i = 0; i < iPtr->numEvents; i++) {
  439.         if (iPtr->events[i].command != NULL) {
  440.         ckfree(iPtr->events[i].command);
  441.         }
  442.     }
  443.     ckfree((char *) iPtr->events);
  444.     iPtr->events = events;
  445.     if (count < iPtr->numEvents) {
  446.         iPtr->curEvent = count-1;
  447.     } else {
  448.         iPtr->curEvent = iPtr->numEvents-1;
  449.     }
  450.     iPtr->numEvents = count;
  451.     return TCL_OK;
  452.     } else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {
  453.     if (argc != 2) {
  454.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  455.             " nextid\"", (char *) NULL);
  456.         return TCL_ERROR;
  457.     }
  458.     sprintf(iPtr->result, "%d", iPtr->curEventNum+1);
  459.     return TCL_OK;
  460.     } else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {
  461.     if (argc > 3) {
  462.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  463.             " redo ?event?\"", (char *) NULL);
  464.         return TCL_ERROR;
  465.     }
  466.     eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
  467.     if (eventPtr == NULL) {
  468.         return TCL_ERROR;
  469.     }
  470.     RevCommand(iPtr, eventPtr->command);
  471.     return Tcl_Eval(interp, eventPtr->command, 0, (char **) NULL);
  472.     } else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {
  473.     if ((argc > 5) || (argc < 4)) {
  474.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  475.             " substitute old new ?event?\"", (char *) NULL);
  476.         return TCL_ERROR;
  477.     }
  478.     eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);
  479.     if (eventPtr == NULL) {
  480.         return TCL_ERROR;
  481.     }
  482.     return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);
  483.     } else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {
  484.     char *words;
  485.  
  486.     if ((argc != 3) && (argc != 4)) {
  487.         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  488.             " words num-num/pat ?event?\"", (char *) NULL);
  489.         return TCL_ERROR;
  490.     }
  491.     eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);
  492.     if (eventPtr == NULL) {
  493.         return TCL_ERROR;
  494.     }
  495.     words = GetWords(iPtr, eventPtr->command, argv[2]);
  496.     if (words == NULL) {
  497.         return TCL_ERROR;
  498.     }
  499.     RevResult(iPtr, words);
  500.     iPtr->result = words;
  501.     iPtr->freeProc = (Tcl_FreeProc *) free;
  502.     return TCL_OK;
  503.     }
  504.  
  505.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  506.         "\": must be add, change, event, info, keep, nextid, ",
  507.         "redo, substitute, or words", (char *) NULL);
  508.     return TCL_ERROR;
  509. }
  510.  
  511. /*
  512.  *----------------------------------------------------------------------
  513.  *
  514.  * MakeSpace --
  515.  *
  516.  *    Given a history event, make sure it has enough space for
  517.  *    a string of a given length (enlarge the string area if
  518.  *    necessary).
  519.  *
  520.  * Results:
  521.  *    None.
  522.  *
  523.  * Side effects:
  524.  *    More memory may get allocated.
  525.  *
  526.  *----------------------------------------------------------------------
  527.  */
  528.  
  529. static void
  530. MakeSpace(hPtr, size)
  531.     HistoryEvent *hPtr;
  532.     int size;            /* # of bytes needed in hPtr. */
  533. {
  534.     if (hPtr->bytesAvl < size) {
  535.     ckfree(hPtr->command);
  536.     hPtr->command = (char *) ckalloc((unsigned) size);
  537.     hPtr->bytesAvl = size;
  538.     }
  539. }
  540.  
  541. /*
  542.  *----------------------------------------------------------------------
  543.  *
  544.  * InsertRev --
  545.  *
  546.  *    Add a new revision to the list of those pending for iPtr.
  547.  *    Do it in a way that keeps the revision list sorted in
  548.  *    increasing order of firstIndex.  Also, eliminate revisions
  549.  *    that are subsets of other revisions.
  550.  *
  551.  * Results:
  552.  *    None.
  553.  *
  554.  * Side effects:
  555.  *    RevPtr is added to iPtr's revision list.
  556.  *
  557.  *----------------------------------------------------------------------
  558.  */
  559.  
  560. static void
  561. InsertRev(iPtr, revPtr)
  562.     Interp *iPtr;            /* Interpreter to use. */
  563.     register HistoryRev *revPtr;    /* Revision to add to iPtr's list. */
  564. {
  565.     register HistoryRev *curPtr;
  566.     register HistoryRev *prevPtr;
  567.  
  568.     for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL;
  569.         prevPtr = curPtr, curPtr = curPtr->nextPtr) {
  570.     /*
  571.      * If this revision includes the new one (or vice versa) then
  572.      * just eliminate the one that is a subset of the other.
  573.      */
  574.  
  575.     if ((revPtr->firstIndex <= curPtr->firstIndex)
  576.         && (revPtr->lastIndex >= curPtr->firstIndex)) {
  577.         curPtr->firstIndex = revPtr->firstIndex;
  578.         curPtr->lastIndex = revPtr->lastIndex;
  579.         curPtr->newSize = revPtr->newSize;
  580.         ckfree(curPtr->newBytes);
  581.         curPtr->newBytes = revPtr->newBytes;
  582.         ckfree((char *) revPtr);
  583.         return;
  584.     }
  585.     if ((revPtr->firstIndex >= curPtr->firstIndex)
  586.         && (revPtr->lastIndex <= curPtr->lastIndex)) {
  587.         ckfree(revPtr->newBytes);
  588.         ckfree((char *) revPtr);
  589.         return;
  590.     }
  591.  
  592.     if (revPtr->firstIndex < curPtr->firstIndex) {
  593.         break;
  594.     }
  595.     }
  596.  
  597.     /*
  598.      * Insert revPtr just after prevPtr.
  599.      */
  600.  
  601.     if (prevPtr == NULL) {
  602.     revPtr->nextPtr = iPtr->revPtr;
  603.     iPtr->revPtr = revPtr;
  604.     } else {
  605.     revPtr->nextPtr = prevPtr->nextPtr;
  606.     prevPtr->nextPtr = revPtr;
  607.     }
  608. }
  609.  
  610. /*
  611.  *----------------------------------------------------------------------
  612.  *
  613.  * RevCommand --
  614.  *
  615.  *    This procedure is invoked by the "history" command to record
  616.  *    a command revision.  See the comments at the beginning of the
  617.  *    file for more information about revisions.
  618.  *
  619.  * Results:
  620.  *    None.
  621.  *
  622.  * Side effects:
  623.  *    Revision information is recorded.
  624.  *
  625.  *----------------------------------------------------------------------
  626.  */
  627.  
  628. static void
  629. RevCommand(iPtr, string)
  630.     register Interp *iPtr;    /* Interpreter in which to perform the
  631.                  * substitution. */
  632.     char *string;        /* String to substitute. */
  633. {
  634.     register HistoryRev *revPtr;
  635.  
  636.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  637.     return;
  638.     }
  639.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  640.     revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst;
  641.     revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst;
  642.     revPtr->newSize = strlen(string);
  643.     revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1));
  644.     strcpy(revPtr->newBytes, string);
  645.     InsertRev(iPtr, revPtr);
  646. }
  647.  
  648. /*
  649.  *----------------------------------------------------------------------
  650.  *
  651.  * RevResult --
  652.  *
  653.  *    This procedure is invoked by the "history" command to record
  654.  *    a result revision.  See the comments at the beginning of the
  655.  *    file for more information about revisions.
  656.  *
  657.  * Results:
  658.  *    None.
  659.  *
  660.  * Side effects:
  661.  *    Revision information is recorded.
  662.  *
  663.  *----------------------------------------------------------------------
  664.  */
  665.  
  666. static void
  667. RevResult(iPtr, string)
  668.     register Interp *iPtr;    /* Interpreter in which to perform the
  669.                  * substitution. */
  670.     char *string;        /* String to substitute. */
  671. {
  672.     register HistoryRev *revPtr;
  673.     char *evalFirst, *evalLast;
  674.     char *argv[2];
  675.  
  676.     if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
  677.     return;
  678.     }
  679.  
  680.     /*
  681.      * Expand the replacement range to include the brackets that surround
  682.      * the command.  If there aren't any brackets (i.e. this command was
  683.      * invoked at top-level) then don't do any revision.  Also, if there
  684.      * are several commands in brackets, of which this is just one,
  685.      * then don't do any revision.
  686.      */
  687.  
  688.     evalFirst = iPtr->evalFirst;
  689.     evalLast = iPtr->evalLast + 1;
  690.     while (1) {
  691.     if (evalFirst == iPtr->historyFirst) {
  692.         return;
  693.     }
  694.     evalFirst--;
  695.     if (*evalFirst == '[') {
  696.         break;
  697.     }
  698.     if (!isspace(*evalFirst)) {
  699.         return;
  700.     }
  701.     }
  702.     if (*evalLast != ']') {
  703.     return;
  704.     }
  705.  
  706.     revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
  707.     revPtr->firstIndex = evalFirst - iPtr->historyFirst;
  708.     revPtr->lastIndex = evalLast - iPtr->historyFirst;
  709.     argv[0] = string;
  710.     revPtr->newBytes = Tcl_Merge(1, argv);
  711.     revPtr->newSize = strlen(revPtr->newBytes);
  712.     InsertRev(iPtr, revPtr);
  713. }
  714.  
  715. /*
  716.  *----------------------------------------------------------------------
  717.  *
  718.  * DoRevs --
  719.  *
  720.  *    This procedure is called to apply the history revisions that
  721.  *    have been recorded in iPtr.
  722.  *
  723.  * Results:
  724.  *    None.
  725.  *
  726.  * Side effects:
  727.  *    The most recent entry in the history for iPtr may be modified.
  728.  *
  729.  *----------------------------------------------------------------------
  730.  */
  731.  
  732. static void
  733. DoRevs(iPtr)
  734.     register Interp *iPtr;    /* Interpreter whose history is to
  735.                  * be modified. */
  736. {
  737.     register HistoryRev *revPtr;
  738.     register HistoryEvent *eventPtr;
  739.     char *newCommand, *p;
  740.     unsigned int size;
  741.     int bytesSeen, count;
  742.  
  743.     if (iPtr->revPtr == NULL) {
  744.     return;
  745.     }
  746.  
  747.     /*
  748.      * The revision is done in two passes.  The first pass computes the
  749.      * amount of space needed for the revised event, and the second pass
  750.      * pieces together the new event and frees up the revisions.
  751.      */
  752.  
  753.     eventPtr = &iPtr->events[iPtr->curEvent];
  754.     size = strlen(eventPtr->command) + 1;
  755.     for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) {
  756.     size -= revPtr->lastIndex + 1 - revPtr->firstIndex;
  757.     size += revPtr->newSize;
  758.     }
  759.  
  760.     newCommand = (char *) ckalloc(size);
  761.     p = newCommand;
  762.     bytesSeen = 0;
  763.     for (revPtr = iPtr->revPtr; revPtr != NULL; ) {
  764.     HistoryRev *nextPtr = revPtr->nextPtr;
  765.  
  766.     count = revPtr->firstIndex - bytesSeen;
  767.     if (count > 0) {
  768.         strncpy(p, eventPtr->command + bytesSeen, count);
  769.         p += count;
  770.     }
  771.     strncpy(p, revPtr->newBytes, revPtr->newSize);
  772.     p += revPtr->newSize;
  773.     bytesSeen = revPtr->lastIndex+1;
  774.     ckfree(revPtr->newBytes);
  775.     ckfree((char *) revPtr);
  776.     revPtr = nextPtr;
  777.     }
  778.     if (&p[strlen(&eventPtr->command[bytesSeen]) + 1] >
  779.         &newCommand[size]) {
  780.     printf("Assertion failed!\n");
  781.     }
  782.     strcpy(p, eventPtr->command + bytesSeen);
  783.  
  784.     /*
  785.      * Replace the command in the event.
  786.      */
  787.  
  788.     ckfree(eventPtr->command);
  789.     eventPtr->command = newCommand;
  790.     eventPtr->bytesAvl = size;
  791.     iPtr->revPtr = NULL;
  792. }
  793.  
  794. /*
  795.  *----------------------------------------------------------------------
  796.  *
  797.  * GetEvent --
  798.  *
  799.  *    Given a textual description of an event (see the manual page
  800.  *    for legal values) find the corresponding event and return its
  801.  *    command string.
  802.  *
  803.  * Results:
  804.  *    The return value is a pointer to the event named by "string".
  805.  *    If no such event exists, then NULL is returned and an error
  806.  *    message is left in iPtr.
  807.  *
  808.  * Side effects:
  809.  *    None.
  810.  *
  811.  *----------------------------------------------------------------------
  812.  */
  813.  
  814. static HistoryEvent *
  815. GetEvent(iPtr, string)
  816.     register Interp *iPtr;    /* Interpreter in which to look. */
  817.     char *string;        /* Description of event. */
  818. {
  819.     int eventNum, index;
  820.     register HistoryEvent *eventPtr;
  821.     int length;
  822.  
  823.     /*
  824.      * First check for a numeric specification of an event.
  825.      */
  826.  
  827.     if (isdigit(*string) || (*string == '-')) {
  828.     if (Tcl_GetInt((Tcl_Interp *) iPtr, string, &eventNum) != TCL_OK) {
  829.         return NULL;
  830.     }
  831.     if (eventNum < 0) {
  832.         eventNum += iPtr->curEventNum;
  833.         }
  834.     if (eventNum > iPtr->curEventNum) {
  835.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  836.             "\" hasn't occurred yet", (char *) NULL);
  837.         return NULL;
  838.     }
  839.     if ((eventNum <= iPtr->curEventNum-iPtr->numEvents)
  840.         || (eventNum <= 0)) {
  841.         Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
  842.             "\" is too far in the past", (char *) NULL);
  843.         return NULL;
  844.     }
  845.     index = iPtr->curEvent + (eventNum - iPtr->curEventNum);
  846.     if (index < 0) {
  847.         index += iPtr->numEvents;
  848.     }
  849.     return &iPtr->events[index];
  850.     }
  851.  
  852.     /*
  853.      * Next, check for an event that contains the string as a prefix or
  854.      * that matches the string in the sense of Tcl_StringMatch.
  855.      */
  856.  
  857.     length = strlen(string);
  858.     for (index = iPtr->curEvent - 1; ; index--) {
  859.     if (index < 0) {
  860.         index += iPtr->numEvents;
  861.     }
  862.     if (index == iPtr->curEvent) {
  863.         break;
  864.     }
  865.     eventPtr = &iPtr->events[index];
  866.     if ((strncmp(eventPtr->command, string, length) == 0)
  867.         || Tcl_StringMatch(eventPtr->command, string)) {
  868.         return eventPtr;
  869.     }
  870.     }
  871.  
  872.     Tcl_AppendResult((Tcl_Interp *) iPtr, "no event matches \"", string,
  873.         "\"", (char *) NULL);
  874.     return NULL;
  875. }
  876.  
  877. /*
  878.  *----------------------------------------------------------------------
  879.  *
  880.  * SubsAndEval --
  881.  *
  882.  *    Generate a new command by making a textual substitution in
  883.  *    the "cmd" argument.  Then execute the new command.
  884.  *
  885.  * Results:
  886.  *    The return value is a standard Tcl error.
  887.  *
  888.  * Side effects:
  889.  *    History gets revised if the substitution is occurring on
  890.  *    a recorded command line.  Also, the re-executed command
  891.  *    may produce side-effects.
  892.  *
  893.  *----------------------------------------------------------------------
  894.  */
  895.  
  896. static int
  897. SubsAndEval(iPtr, cmd, old, new)
  898.     register Interp *iPtr;    /* Interpreter in which to execute
  899.                  * new command. */
  900.     char *cmd;            /* Command in which to substitute. */
  901.     char *old;            /* String to search for in command. */
  902.     char *new;            /* Replacement string for "old". */
  903. {
  904.     char *src, *dst, *newCmd;
  905.     int count, oldLength, newLength, length, result;
  906.  
  907.     /*
  908.      * Figure out how much space it will take to hold the
  909.      * substituted command (and complain if the old string
  910.      * doesn't appear in the original command).
  911.      */
  912.  
  913.     oldLength = strlen(old);
  914.     newLength = strlen(new);
  915.     src = cmd;
  916.     count = 0;
  917.     while (1) {
  918.     src = strstr(src, old);
  919.     if (src == NULL) {
  920.         break;
  921.     }
  922.     src += oldLength;
  923.     count++;
  924.     }
  925.     if (count == 0) {
  926.     Tcl_AppendResult((Tcl_Interp *) iPtr, "\"", old,
  927.         "\" doesn't appear in event", (char *) NULL);
  928.     return TCL_ERROR;
  929.     }
  930.     length = strlen(cmd) + count*(newLength - oldLength);
  931.  
  932.     /*
  933.      * Generate a substituted command.
  934.      */
  935.  
  936.     newCmd = (char *) ckalloc((unsigned) (length + 1));
  937.     dst = newCmd;
  938.     while (1) {
  939.     src = strstr(cmd, old);
  940.     if (src == NULL) {
  941.         strcpy(dst, cmd);
  942.         break;
  943.     }
  944.     strncpy(dst, cmd, src-cmd);
  945.     dst += src-cmd;
  946.     strcpy(dst, new);
  947.     dst += newLength;
  948.     cmd = src + oldLength;
  949.     }
  950.  
  951.     RevCommand(iPtr, newCmd);
  952.     result = Tcl_Eval((Tcl_Interp *) iPtr, newCmd, 0, (char **) NULL);
  953.     ckfree(newCmd);
  954.     return result;
  955. }
  956.  
  957. /*
  958.  *----------------------------------------------------------------------
  959.  *
  960.  * GetWords --
  961.  *
  962.  *    Given a command string, return one or more words from the
  963.  *    command string.
  964.  *
  965.  * Results:
  966.  *    The return value is a pointer to a dynamically-allocated
  967.  *    string containing the words of command specified by "words".
  968.  *    If the word specifier has improper syntax then an error
  969.  *    message is placed in iPtr->result and NULL is returned.
  970.  *
  971.  * Side effects:
  972.  *    Memory is allocated.  It is the caller's responsibilty to
  973.  *    free the returned string..
  974.  *
  975.  *----------------------------------------------------------------------
  976.  */
  977.  
  978. static char *
  979. GetWords(iPtr, command, words)
  980.     register Interp *iPtr;    /* Tcl interpreter in which to place
  981.                  * an error message if needed. */
  982.     char *command;        /* Command string. */
  983.     char *words;        /* Description of which words to extract
  984.                  * from the command.  Either num[-num] or
  985.                  * a pattern. */
  986. {
  987.     char *result;
  988.     char *start, *end, *dst;
  989.     register char *next;
  990.     int first;            /* First word desired. -1 means last word
  991.                  * only. */
  992.     int last;            /* Last word desired.  -1 means use everything
  993.                  * up to the end. */
  994.     int index;            /* Index of current word. */
  995.     char *pattern;
  996.  
  997.     /*
  998.      * Figure out whether we're looking for a numerical range or for
  999.      * a pattern.
  1000.      */
  1001.  
  1002.     pattern = NULL;
  1003.     first = 0;
  1004.     last = -1;
  1005.     if (*words == '$') {
  1006.     if (words[1] != '\0') {
  1007.         goto error;
  1008.     }
  1009.     first = -1;
  1010.     } else if (isdigit(*words)) {
  1011.     first = strtoul(words, &start, 0);
  1012.     if (*start == 0) {
  1013.         last = first;
  1014.     } else if (*start == '-') {
  1015.         start++;
  1016.         if (*start == '$') {
  1017.         start++;
  1018.         } else if (isdigit(*start)) {
  1019.         last = strtoul(start, &start, 0);
  1020.         } else {
  1021.         goto error;
  1022.         }
  1023.         if (*start != 0) {
  1024.         goto error;
  1025.         }
  1026.     }
  1027.     if ((first > last) && (last != -1)) {
  1028.         goto error;
  1029.     }
  1030.     } else {
  1031.     pattern = words;
  1032.     }
  1033.  
  1034.     /*
  1035.      * Scan through the words one at a time, copying those that are
  1036.      * relevant into the result string.  Allocate a result area large
  1037.      * enough to hold all the words if necessary.
  1038.      */
  1039.  
  1040.     result = (char *) ckalloc((unsigned) (strlen(command) + 1));
  1041.     dst = result;
  1042.     for (next = command; isspace(*next); next++) {
  1043.     /* Empty loop body:  just find start of first word. */
  1044.     }
  1045.     for (index = 0; *next != 0; index++) {
  1046.     start = next;
  1047.     end = TclWordEnd(next, 0);
  1048.     if (*end != 0) {
  1049.         end++;
  1050.         for (next = end; isspace(*next); next++) {
  1051.         /* Empty loop body:  just find start of next word. */
  1052.         }
  1053.     }
  1054.     if ((first > index) || ((first == -1) && (*next != 0))) {
  1055.         continue;
  1056.     }
  1057.     if ((last != -1) && (last < index)) {
  1058.         continue;
  1059.     }
  1060.     if (pattern != NULL) {
  1061.         int match;
  1062.         char savedChar = *end;
  1063.  
  1064.         *end = 0;
  1065.         match = Tcl_StringMatch(start, pattern);
  1066.         *end = savedChar;
  1067.         if (!match) {
  1068.         continue;
  1069.         }
  1070.     }
  1071.     if (dst != result) {
  1072.         *dst = ' ';
  1073.         dst++;
  1074.     }
  1075.     strncpy(dst, start, (end-start));
  1076.     dst += end-start;
  1077.     }
  1078.     *dst = 0;
  1079.  
  1080.     /*
  1081.      * Check for an out-of-range argument index.
  1082.      */
  1083.  
  1084.     if ((last >= index) || (first >= index)) {
  1085.     ckfree(result);
  1086.     Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words,
  1087.         "\" specified non-existent words", (char *) NULL);
  1088.     return NULL;
  1089.     }
  1090.     return result;
  1091.  
  1092.     error:
  1093.     Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words,
  1094.         "\":  should be num-num or pattern", (char *) NULL);
  1095.     return NULL;
  1096. }
  1097.